﻿namespace Caltrain.ViewModel
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Diagnostics;
    using System.Threading;
    using System.Windows;
    using System.Windows.Threading;
    using Caltrain.Model;
    using GalaSoft.MvvmLight;
    using GalaSoft.MvvmLight.Command;
    using System.IO.IsolatedStorage;
    using System.Windows.Input;

    /// <summary>
    /// This class contains properties that the main View can data bind to.
    /// <para>
    /// Use the <strong>mvvminpc</strong> snippet to add bindable properties to this ViewModel.
    /// </para>
    /// <para>
    /// You can also use Blend to data bind with the tool's support.
    /// </para>
    /// <para>
    /// See http://www.galasoft.ch/mvvm/getstarted
    /// </para>
    /// </summary>
    public class MainViewModel : ViewModelBase
    {
        private ObservableCollection<string> stations;
        private string selectedSourceStation;
        private string selectedDestinationStation;
        private ScheduleManager scheduleManager;

        /// <summary>
        /// The <see cref="TrainSchedules" /> property's name.
        /// </summary>
        public const string TrainSchedulesPropertyName = "TrainSchedules";

        /// <summary>
        /// Train schedules
        /// </summary>
        private ObservableCollection<TrainSchedule> trainSchedules;

        /// <summary>
        /// Gets the TrainSchedules property.
        /// </summary>
        public ObservableCollection<TrainSchedule> TrainSchedules
        {
            get
            {
                return trainSchedules;
            }

            set
            {
                if (trainSchedules == value)
                {
                    return;
                }

                var oldValue = trainSchedules;
                trainSchedules = value;

                // Update bindings, no broadcast
                RaisePropertyChanged(TrainSchedulesPropertyName);
            }
        }

        public string ApplicationTitle
        {
            get
            {
                return "Caltrain";
            }
        }

        public ObservableCollection<string> Stations
        {
            get
            {
                return this.stations;
            }
            set
            {
                this.stations = value;
                base.RaisePropertyChanged("Stations");
            }
        }

        public string SelectedStartStation
        {
            get
            {
                return this.selectedSourceStation;
            }
            set
            {
                this.selectedSourceStation = value;
                base.RaisePropertyChanged("SelectedStartStation");
                ThreadPool.QueueUserWorkItem((o) => SaveAndRecomputeTimings());
            }
        }

        public string SelectedDestinationStation
        {
            get
            {
                return this.selectedDestinationStation;
            }
            set
            {
                this.selectedDestinationStation = value;
                base.RaisePropertyChanged("SelectedDestinationStation");
                ThreadPool.QueueUserWorkItem((o) => SaveAndRecomputeTimings());
            }
        }

        /// <summary>
        /// The <see cref="TravelDate" /> property's name.
        /// </summary>
        public const string TravelDatePropertyName = "TravelDate";

        private DateTime travelDate;

        /// <summary>
        /// Gets the TravelDate property.
        /// TODO Update documentation:
        /// Changes to that property's value raise the PropertyChanged event. 
        /// This property's value is broadcasted by the Messenger's default instance when it changes.
        /// </summary>
        public DateTime TravelDate
        {
            get
            {
                return travelDate;
            }

            set
            {
                this.travelDate = value;
                base.RaisePropertyChanged("TravelDate");
                ThreadPool.QueueUserWorkItem((o) => SaveAndRecomputeTimings());
            }
        }

        public ICommand Swap
        {
            get
            {
                return new RelayCommand(() =>
                {
                    string temp = this.selectedSourceStation;
                    this.selectedSourceStation = this.selectedDestinationStation;
                    this.selectedDestinationStation = temp;
                    base.RaisePropertyChanged("SelectedStartStation");
                    base.RaisePropertyChanged("SelectedDestinationStation");
                    ThreadPool.QueueUserWorkItem((o) => SaveAndRecomputeTimings());
                });
            }
        }

        /// <summary>
        /// The <see cref="SelectedTrainSchedule" /> property's name.
        /// </summary>
        public const string SelectedTrainSchedulePropertyName = "SelectedTrainSchedule";

        private TrainSchedule selectedTrainSchedule = null;

        /// <summary>
        /// Gets the SelectedTrainSchedule property.
        /// </summary>
        public TrainSchedule SelectedTrainSchedule
        {
            get
            {
                return selectedTrainSchedule;
            }

            set
            {
                if (selectedTrainSchedule == value)
                {
                    return;
                }

                var oldValue = selectedTrainSchedule;
                selectedTrainSchedule = value;

                // Update bindings, no broadcast
                RaisePropertyChanged(SelectedTrainSchedulePropertyName);
            }
        }

        /// <summary>
        /// Initializes a new instance of the MainViewModel class.
        /// </summary>
        public MainViewModel()
        {
            scheduleManager = new ScheduleManager();
            scheduleManager.Initialize();

            this.Stations = new ObservableCollection<string>();
            var modelStations = scheduleManager.GetStations();
            foreach (string station in modelStations)
            {
                this.Stations.Add(station.Remove(station.LastIndexOf(" Caltrain")));
            }

            if (IsolatedStorageSettings.ApplicationSettings.Contains("Start"))
            {
                this.selectedSourceStation = (string)IsolatedStorageSettings.ApplicationSettings["Start"];
                this.selectedDestinationStation = (string)IsolatedStorageSettings.ApplicationSettings["End"];
            }
            else
            {
                this.selectedSourceStation = this.Stations[0];
                this.selectedDestinationStation = this.Stations[this.Stations.Count - 1];
            }

            base.RaisePropertyChanged("Stations");

            if (IsolatedStorageSettings.ApplicationSettings.Contains("TravelDate"))
            {
                this.travelDate = (DateTime)IsolatedStorageSettings.ApplicationSettings["TravelDate"];
            }
            else
            {
                this.travelDate = DateTime.Today;
            }

            base.RaisePropertyChanged("TravelDate");

            this.SaveAndRecomputeTimings();

            this.TrainSchedules = new ObservableCollection<TrainSchedule>();
            if (IsInDesignMode)
            {
                // Code runs in Blend --> create design time data.
            }
            else
            {
            }
        }

        /// <summary>
        /// Save stations to ISO storage and recompute timings
        /// </summary>
        public void SaveAndRecomputeTimings()
        {
            // Determine next train and set it
            string startStation = this.SelectedStartStation + " Caltrain";
            string endStation = this.SelectedDestinationStation + " Caltrain";
            bool nextTrainChosen = false;
            var newSchedule = new ObservableCollection<TrainSchedule>();
            TrainSchedule nextTrainSchedule = null;
            var validTrips = this.scheduleManager.GetSchedule(startStation, endStation, this.TravelDate);
            foreach (var trip in validTrips)
            {
                DateTime departure = DateTime.Today + trip.Key.DepartureTime;
                DateTime arrival = DateTime.Today + trip.Value.ArrivalTime;
                bool isNextTrain = false;
                if (!nextTrainChosen && departure > DateTime.Now)
                {
                    nextTrainChosen = true;
                    isNextTrain = true;
                }

                var trainSchedule = new TrainSchedule(departure, arrival, isNextTrain, String.Format("#{0}", trip.Key.TrainId));
                newSchedule.Add(trainSchedule);

                if (isNextTrain)
                {
                    nextTrainSchedule = trainSchedule;
                }
            }
            
            // For some reason, this crashes VS
            if (!IsInDesignMode)
            {
                Deployment.Current.Dispatcher.BeginInvoke(() => this.TrainSchedules = newSchedule);
                if (nextTrainChosen)
                {
                    Deployment.Current.Dispatcher.BeginInvoke(() => this.SelectedTrainSchedule = nextTrainSchedule);
                }
            }

            IsolatedStorageSettings.ApplicationSettings["Start"] = this.SelectedStartStation;
            IsolatedStorageSettings.ApplicationSettings["End"] = this.SelectedDestinationStation;
            IsolatedStorageSettings.ApplicationSettings["TravelDate"] = this.TravelDate;
        }
    }
}